#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "ColorPicker.h"

#include "Class.h"
#include "ScrollBar.h"
#include "Slider.h"
#include "WindowPort.h"
#include "Window.h"
#include "Buttons.h"
#include "Filler.h"
#include "Look.h"
#include "BorderItems.h"
#include "Fields.h"
#include "Math.h"
#include "PopupItem.h"
#include "OrdColl.h"
#include "CollView.h"
#include "Menu.h"

enum {
	cCpValAlphaSlider,
	cCpRGBSlider,
	cCpPicker,
	cCpWheel,
	cCpReset,
	cCpHueSatNum,
	cCpSlider,
	cCpNumField,
	cCpPopup,
	cCpCancel,
	cCpSetColor
};
IdRange ColorPickerRange(cCpSetColor+1);

static ColorPicker *gPicker;

ONEXIT(ColorPicker)
{
	SafeDelete(gPicker);
}

//---- ColorCell ---------------------------------------------------------------

class ColorCell: public VObject {
	RGBColor *rc;
public:
	MetaDef(ColorCell);
	ColorCell(RGB c, Point e, bool hfix= TRUE);
	ColorCell(RGBColor *c, Point e, bool hfix= TRUE);
	~ColorCell();
	void DrawAll(Rectangle r, bool highlight);
	void SetColor(RGB c);
	RGB GetColor()
		{ return *rc->GetRGB(); }
};

//---- ColorCell ---------------------------------------------------------------

NewMetaImpl(ColorCell,VObject, (TP(rc)));

ColorCell::~ColorCell()
{
	SafeDelete(rc);
}

void ColorCell::SetColor(RGB c)
{
	RGBColor *rr= (RGBColor*) gLook->GetBackgroundColor();
	RGB d, b(*rr->GetRGB());

	d.red= (b.red*(255-c.alpha) + c.red*c.alpha)/255;
	d.green= (b.green*(255-c.alpha) + c.green*c.alpha)/255;
	d.blue= (b.blue*(255-c.alpha) + c.blue*c.alpha)/255;

	if (rc->SetRGB(&d, MaxWord))
		ForceRedraw();
}

ColorCell::ColorCell(RGB c, Point e, bool hfix) : VObject(e)
{
	rc= new RGBColor(c);
	rc->SetPrec(MaxWord);
	SetFlag(eVObjVFixed);
	SetFlag(eVObjHFixed, hfix);
}

ColorCell::ColorCell(RGBColor *c, Point e, bool hfix) : VObject(e)
{
	rc= c;
	rc->SetPrec(MaxWord);
	SetFlag(eVObjVFixed);
	SetFlag(eVObjHFixed, hfix);
}

void ColorCell::DrawAll(Rectangle r, bool highlight)
{
	if (IsOpen() && r.Clip(contentRect)) {
		if (highlight || TestFlag(eVObjHighlight)) {
			GrPaintRect(r, gHighlightColor);
			GrPaintRect(contentRect.Inset(2), rc);
		} else
			GrPaintRect(contentRect, rc);
	}
}

//---- CPicker -----------------------------------------------------------------

class CPicker: public Box {
public:
	MetaDef(CPicker);
	CPicker() : Box(ColorPickerRange()+cCpPicker, gPoint1, gPoint0,
										(VObjAlign)(eVObjHExpand+eVObjVExpand))
		{ }
	virtual void SetColor(RGB, bool)
		{ }
	virtual RGB GetColor()
		{ return RGB(0,0,0); }
	virtual VObject *DoMakeContent()
		{ return 0; }
	void SetContainer(VObject *v);
};

NewMetaImpl0(CPicker,Box);

void CPicker::SetContainer(VObject *v)
{
	if (Size() <= 0)
		Add(new BorderItem(AsString(), DoMakeContent()));
	Box::SetContainer(v);
}

//---- ColorSlider -------------------------------------------------------------

class ColorSlider : public VBox {
	IntField *num;
	Slider *slider;
public:
	ColorSlider(int id, char *label);
	void SetVal(int val, bool redraw);
	void Control(int id, int part, void *val);
	int GetVal()
		{ return slider->GetMax().y-slider->GetVal().y; }
};

ColorSlider::ColorSlider(int id, char *label)
						: VBox(gPoint5, (VObjAlign)(eVObjHCenter+eVObjVExpand))
{
	SetId(id);
	slider= new Slider(cCpSlider+ColorPickerRange(), eVert, TRUE);
	slider->SetMax(255, FALSE);
	Add(slider);
	num= new IntField(cCpNumField+ColorPickerRange(), 0, 0, 255);
	Add(new HBox(5, eVObjVBase, new TextItem(label), num, 0));
}

void ColorSlider::SetVal(int val, bool redraw)
{
	slider->SetVal(slider->GetMax().y-val, redraw);
	num->SetValue(val, redraw);
}

void ColorSlider::Control(int id, int part, void *val)
{
	switch (id-ColorPickerRange()) {
	case cCpSlider:
		if (part == eSliderThumb) {
			num->SetValue(slider->GetMax().y-slider->GetVal().y, TRUE);
			Control(GetId(), eSliderThumb, 0);
		}
		break;
	case cCpNumField:
		if (part == cPartValueChanged) {
			slider->SetVal(slider->GetMax().y-num->GetValue(), TRUE);
			Control(GetId(), eSliderThumb, 0);
		}
		break;
	default:
		VBox::Control(id, part, val);
		break;
	}
}

//---- ColorWheel --------------------------------------------------------------

static short StdSectors[] = { 36, 36, 18, 18, 1, 0 };

class ColorWheel: public VObject {
protected:
	short *sectors;
	int circles, maxix, sz;
	HSVColor hsv;
	RGBColor *palette;
	Point border;
	ImageCache cache;
public:
	MetaDef(ColorWheel);
	ColorWheel(int id, short *sectors= StdSectors);
	~ColorWheel();
	void Draw(Rectangle cr);
	Command *DoLeftButtonDownCommand(Point, Token, int);
	void SetColor(HSVColor c, bool redraw);
	void SetColor2(HSVColor c, bool redraw);
	Rectangle DotRect();
	HSVColor MapPointToColor(Point p);
	Metric GetMinSize()
		{ return Metric(245); }
	HSVColor GetColor()
		{ return hsv; }
};

//---- PickerCommand -----------------------------------------------------------

class PickerCommand: public Command {
	ColorWheel *wheel;
public:
	PickerCommand(class ColorWheel *w) : Command("pick")
		{ wheel= w; }
	Command *TrackMouse(TrackPhase, Point ap, Point, Point np);
	void TrackFeedback(Point, Point, bool)
		{ }
};

Command *PickerCommand::TrackMouse(TrackPhase atp, Point, Point, Point np)
{
	wheel->SetColor2(wheel->MapPointToColor(np), TRUE);
	if (atp == eTrackRelease)
		return gNoChanges;
	return this;
}

//---- ColorWheel --------------------------------------------------------------

NewMetaImpl(ColorWheel,VObject, (TV(sectors,circles), T(circles), T(maxix), T(border)));

ColorWheel::ColorWheel(int id, short *sects) : VObject(id)
{
	sectors= sects;
	for (sz= circles= 0; sectors[circles]; circles++)
		sz+= sectors[circles];
	border= 6;
	palette= new RGBColor[sz];
	maxix= 0;
	SetColor(HSVColor(0, MaxWord, MaxWord), FALSE);
	SetFlag(eVObjHFixed|eVObjVFixed);
}

ColorWheel::~ColorWheel()
{
	SafeDelete(palette);
}

HSVColor ColorWheel::MapPointToColor(Point p)
{
	HSVColor hc= hsv;
	double rad= contentRect.Inset(border).extent.x / 2.0;

	hc.hue= contentRect.PointToAngle(p);
	hc.saturation= (int) (Length(p - contentRect.Center()) / rad
													* (double)MaxWord + 0.5);
	return hc;
}

Rectangle ColorWheel::DotRect()
{
	Rectangle mark(13), inner= contentRect.Inset(border);
	double w= (double) inner.extent.x / (double) MaxWord * hsv.saturation / 2.0,
		h= (double) inner.extent.y / (double) MaxWord * hsv.saturation / 2.0;
	mark.origin= contentRect.Center()
					+ PolarToPoint((double)(hsv.hue), w, h) - mark.extent/2;
	return mark;
}

void ColorWheel::SetColor(HSVColor c, bool redraw)
{
	if (hsv.hue != c.hue || hsv.saturation != c.saturation) {
		if (redraw)
			InvalidateRect(DotRect());
		hsv.hue= c.hue;
		hsv.saturation= Math::Min(c.saturation, (short) MaxWord);
		if (redraw)
			InvalidateRect(DotRect());
	}
	if (c.value != hsv.value || c.alpha != hsv.alpha) {
		int i, cir;
		bool invalidate= FALSE;

		RGBColor *rr= (RGBColor*) gLook->GetBackgroundColor();
		RGB b(*rr->GetRGB());
		
		hsv.value= c.value;
		hsv.alpha= c.alpha;
		
		for (i= cir= 0; cir < circles; cir++) {
			int sat= (MaxWord * (circles-1-cir)) / circles;
			for (int h= 0; h < sectors[cir]; h++) {
				int hue= (360 * h + (360/sectors[cir]/2)) / sectors[cir];
				HSVColor h(hue, sat, c.value);
				RGB d(h);
				
				d.red= (b.red*(255-c.alpha) + d.red*c.alpha)/255;
				d.green= (b.green*(255-c.alpha) + d.green*c.alpha)/255;
				d.blue= (b.blue*(255-c.alpha) + d.blue*c.alpha)/255;
				
				if (palette[i].SetRGB(&d, MaxWord))
					invalidate= TRUE;
				i++;
			}
		}

		if (invalidate) {
			ForceRedraw();
			cache.Invalidate();
		}
	}
}

void ColorWheel::SetColor2(HSVColor c, bool redraw)
{
	if (hsv.hue != c.hue || hsv.saturation != c.saturation) {
		SetColor(c, redraw);
		Control(GetId(), cPartValueChanged, (void*) &hsv);
	}
}

void ColorWheel::Draw(Rectangle)
{
	//if (cache.Open(cr)) {
		Rectangle r(contentRect.Inset(border));
		Point Step= r.extent/(2*circles-1);
		int sat, i;

		for (i= sat= 0; sat < circles; sat++) {
			for (int hue= 0; hue < sectors[sat]; hue++)
				GrPaintWedge(r, 360 * hue / sectors[sat], 360/sectors[sat],
										&palette[i++]);
			r= r.Inset(Step);
		}
		GrSetPenNormal();
		GrSetPenSize(2);
		GrStrokeOval(contentRect.Inset(border));
		//cache.Close();
	//}

	GrSetPenSize(3);
	GrSetPenInk(gInkWhite);
	GrStrokeOval(DotRect());
	GrSetPenNormal();
	GrSetPenInk(gInkBlack);
	GrStrokeOval(DotRect().Inset(1));
}

Command *ColorWheel::DoLeftButtonDownCommand(Point, Token, int)
{
	return new PickerCommand(this);
}

//---- HSVPicker ---------------------------------------------------------------

class HSVPicker: public CPicker {
	ColorSlider *valslider, *alpha;
	ColorWheel *wheel;
	IntField *hNum, *sNum;
public:
	MetaDef(HSVPicker);
	HSVPicker() : CPicker()
		{ }
	void SetColor(RGB, bool);
	void Control(int id, int part, void *val);
	RGB GetColor();
	const char *AsString()
		{ return "HSVA"; }
	VObject *DoMakeContent();
};

NewMetaImpl(HSVPicker,CPicker, (TP(valslider), TP(alpha), TP(wheel), TP(hNum), TP(sNum)));

VObject *HSVPicker::DoMakeContent()
{
	wheel= new ColorWheel(cCpWheel+ColorPickerRange());
	valslider= new ColorSlider(cCpValAlphaSlider+ColorPickerRange(), "V:");
	alpha= new ColorSlider(cCpValAlphaSlider+ColorPickerRange(), "A:");
	hNum= new IntField(cCpHueSatNum+ColorPickerRange(), 0, 0, 359);
	sNum= new IntField(cCpHueSatNum+ColorPickerRange(), 0, 0, 255);

	return new Expander(cCpPicker+ColorPickerRange(), eHor, Point(20),
		new VBox(5, eVObjVGapExpand,
			wheel,
			new HBox(10, (VObjAlign)(eVObjVBase+eVObjHGapExpand),
				new HBox(5, eVObjVBase, new TextItem("Hue:"), hNum, 0),
				new HBox(5, eVObjVBase, new TextItem("Saturation:"), sNum, 0),
				0
			),
			0
		),
		valslider,
		alpha,
		0
	);
}

RGB HSVPicker::GetColor()
{
	return HSVColor(hNum->GetValue(), sNum->GetValue(), valslider->GetVal(), alpha->GetVal());
}

void HSVPicker::SetColor(RGB c, bool redraw)
{
	HSVColor hsv(c);
	wheel->SetColor(hsv, redraw);
	hNum->SetValue(hsv.hue, redraw);
	sNum->SetValue(hsv.saturation, redraw);
	valslider->SetVal(hsv.value, redraw);
	alpha->SetVal(hsv.alpha, redraw);
}

void HSVPicker::Control(int id, int part, void *val)
{
	switch (id-ColorPickerRange()) {
	case cCpHueSatNum:
		if (part == cPartValueChanged) {
			HSVColor color(hNum->GetValue(), sNum->GetValue(), valslider->GetVal(), alpha->GetVal());
			wheel->SetColor(color, TRUE);
			Control(cCpPicker+ColorPickerRange(), cPartValueChanged, 0);
		}
		break;
	case cCpValAlphaSlider:
		if (part == eSliderThumb) {
			HSVColor color(hNum->GetValue(), sNum->GetValue(), valslider->GetVal(), alpha->GetVal());
			wheel->SetColor(color, TRUE);
			Control(cCpPicker+ColorPickerRange(), cPartValueChanged, 0);
		}
		break;
	case cCpWheel:
		if (part == cPartValueChanged) {
			HSVColor color(wheel->GetColor());
			hNum->SetValue(color.hue, TRUE);
			sNum->SetValue(color.saturation, TRUE);
			Control(cCpPicker+ColorPickerRange(), cPartValueChanged, 0);
		}
		break;
	default:
		CPicker::Control(id, part, val);
		break;
	}
}

//---- RGBPicker ---------------------------------------------------------------

class RGBPicker: public CPicker {
	ColorSlider *red, *green, *blue, *alpha;
public:
	MetaDef(RGBPicker);
	RGBPicker() : CPicker()
		{ }
	void SetColor(RGB, bool redraw);
	void Control(int id, int part, void *val);
	RGB GetColor()
		{ return RGB(red->GetVal(), green->GetVal(), blue->GetVal(), alpha->GetVal()); }
	const char *AsString()
		{ return "RGBA"; }
	VObject *DoMakeContent();
};

NewMetaImpl(RGBPicker,CPicker, (TP(red), TP(green), TP(blue), TP(alpha)));

VObject *RGBPicker::DoMakeContent()
{
	return new Expander(cCpPicker+ColorPickerRange(), eHor, Point(20),
		red= new ColorSlider(cCpRGBSlider+ColorPickerRange(), "R:"),
		green= new ColorSlider(cCpRGBSlider+ColorPickerRange(), "G:"),
		blue= new ColorSlider(cCpRGBSlider+ColorPickerRange(), "B:"),
		alpha= new ColorSlider(cCpRGBSlider+ColorPickerRange(), "A:"),
		0
	);
}

void RGBPicker::Control(int id, int part, void *val)
{
	if (id == cCpRGBSlider-ColorPickerRange() && part == eSliderThumb)
		CPicker::Control(cCpPicker+ColorPickerRange(), cPartValueChanged, 0);
	else
		CPicker::Control(id, part, val);
}

void RGBPicker::SetColor(RGB color, bool redraw)
{
	red->SetVal(color.red, redraw);
	green->SetVal(color.green, redraw);
	blue->SetVal(color.blue, redraw);
	alpha->SetVal(color.alpha, redraw);
}

//---- PalettePicker -----------------------------------------------------------

class PalettePicker: public CPicker {
	CollectionView *cv;
	RGB c;
public:
	MetaDef(PalettePicker);
	PalettePicker() : CPicker()
		{ }
	RGB GetColor();
	void SetColor(RGB cc, bool)
		{ c= cc; }
	VObject *DoMakeContent();
	const char *AsString()
		{ return "Palette"; }
	void Control(int id, int part, void *val);
};

NewMetaImpl0(PalettePicker,CPicker);

VObject *PalettePicker::DoMakeContent()
{
	OrdCollection *col= new OrdCollection;
	for (int i= 0; i < 256; i++)
		col->Add(new ColorCell(new RGBColorCell(i), Point(15)));
	cv= new CollectionView(this, col, eCVDefault, 16, 16);
	cv->SetId(cCpPicker+ColorPickerRange());
	cv->SetGap(gPoint1);
	return cv;
}

RGB PalettePicker::GetColor()
{
	Rectangle s(cv->GetSelection());
	ColorCell *cc= (ColorCell*) cv->GetItem(s.origin.x, s.origin.y);
	if (cc)
		return cc->GetColor();
	return c;
}

void PalettePicker::Control(int id, int part, void *val)
{
	if (part == cPartCollSelect)
		CPicker::Control(cCpPicker+ColorPickerRange(), cPartValueChanged, 0);
	else
		CPicker::Control(id, part, val);
}

//---- ColorPicker -------------------------------------------------------------

NewMetaImpl(ColorPicker,Dialog, (TP(oldcell), TP(newcell)));
		
ColorPicker::ColorPicker(char *name) : Dialog(name)
{
	current= 0;
	pickers= new OrdCollection;
	pickers->Add(new PalettePicker);
	pickers->Add(new HSVPicker);
	pickers->Add(new RGBPicker);
}

void ColorPicker::DoSetup()
{
	//newcell->SetColor(color);
	//oldcell->SetColor(oldcolor);
}

void ColorPicker::DoSetDefaults()
{
	//newcell->SetColor(color);
	//oldcell->SetColor(oldcolor);
}

VObject *ColorPicker::DoMakeContent()
{
	Iter next(pickers);
	CPicker *p;

	Menu *m= new Menu("Model", FALSE);
	for (int id= 0; p= (CPicker*) next(); id++)
		m->AppendItem(p->AsString(), id);

	RGB rc(color);

	return
		new Matte(
			new HExpander(gPoint10,
				border= new VExpander(gPoint10,
					(CPicker*)pickers->At(current),
					0
				),
				new VExpander(gPoint10,
					new BorderItem("Old/New",
						new VBox(gPoint0, (VObjAlign)(eVObjHExpand),
							oldcell= new ColorCell(rc, Point(75, 45), FALSE),
							newcell= new ColorCell(rc, Point(75, 45), FALSE),
							0
						)
					),
					new Filler(gPoint10),
					new PopupButton(cCpPopup+ColorPickerRange(), current, m),
					new Filler(gPoint10),
					new ActionButton(cCpReset+ColorPickerRange(),  "Reset"),
					new ActionButton(cCpCancel+ColorPickerRange(), "Cancel"),
					new ActionButton(cIdYes+ColorPickerRange(),    "Set Color", TRUE),
					0
				),
				0
			)
		);
}

void ColorPicker::Control(int id, int part, void *val)
{
	CPicker *picker;

	switch (id-ColorPickerRange()) {
	case cCpPopup:
		if (part == cPartToggle && current != (int)val) {
			SetFirstHandler(0);
			current= (int)val;
			picker= (CPicker*) pickers->At(current);
			if (picker) {
				border->SetAt(0, picker);
				picker->SetColor(color, FALSE);
			}
		}
		break;
		
	case cCpPicker:
		if (part == cPartValueChanged) {
			picker= (CPicker*) pickers->At(current);
			color= picker->GetColor();
			newcell->SetColor(color);
		}
		break;
		
	case cCpSetColor:
		newcell->SetColor(*((RGB*)val));
		break;

	case cCpReset:
		color= oldcolor;
		picker= (CPicker*) pickers->At(current);
		picker->SetColor(color, TRUE);
		newcell->SetColor(color);
		id= cIdNone;
		break;

	case cCpCancel:
		color= oldcolor;
		id= cIdCancel;
		break;
	}
	Dialog::Control(id, part, val);
}

RGB ColorPicker::PickColor(const RGB &c)
{
	color= c;
	oldcolor= c;
	Dialog::ShowUnderMouse();
	return color;
}

RGB PickColor(const RGB &c)
{
	if (gPicker == 0)
		gPicker= new ColorPicker("Color Picker");
	return gPicker->PickColor(c);
}

